<template lang="html">
  <div>
    <log-filter
      ref="standartLogFilter"
      :key="routeChangeIndex"
      :env-list="envList"
      :stream-list="streamList"
      :process-list="processList"
      :log-count="streamLogCount"
      :loading="isStreamLogListLoading"
      :type="'standartLog'"
      :is-use-stream-filter="false"
      @change="handleLogSearch"
      @date-change="handlePickSuccess"
      @reload="handleLogReload"
    />

    <div
      v-if="streamLogFilters.length"
      class="table-filters"
    >
      <ul class="filter-list">
        <li
          v-for="(filter) of streamLogFilters"
          :key="filter.value"
        >
          <span class="filter-value">{{ $t('实例名') }}: {{ filter.text }}</span>
        </li>
      </ul>
      <span
        v-if="streamLogFilters.length"
        v-bk-tooltips.right="$t('清空筛选条件')"
        class="paasng-icon paasng-close-circle-shape clear-filters-btn"
        @click="handleClearStreamLogFilters"
      />
    </div>

    <div class="ps-log-header">
      <bk-switcher
        v-model="isShowDate"
        :disabled="!streamLogList.length"
        class="bk-small-switcher"
      />
      <span class="text">{{ isShowDate ? $t('隐藏时间') : $t('显示时间') }}</span>
    </div>
    <div
      ref="logContainer"
      v-bkloading="{ isLoading: isStreamLogListLoading && !isScrollLoading }"
      class="ps-log-container"
      :style="{ 'height': `${contentHeight}px`, 'overflow': isStreamLogListLoading ? 'hidden' : 'auto' }"
    >
      <div
        v-if="isScrollLoading"
        v-bkloading="{ isLoading: isScrollLoading, color: '#24252c' }"
        class="scroll-loading"
      />
      <p
        v-if="streamLogList.length && !hasNextStreamLog"
        class="no-data"
      >
        {{ $t('已加载该时间段内所有日志') }}
      </p>
      <ul v-if="streamLogList.length">
        <li
          v-for="(log, index) of streamLogList"
          :key="index"
          class="stream-log"
        >
          <span
            v-if="isShowDate"
            class="mr10"
            style="min-width: 140px;"
          >{{ formatTime(log.timestamp) }}</span>
          <div>
            <span
              v-if="log.process_id.length < 5"
              class="mouseStyle"
            >{{ log.process_id }}</span>
            <span
              v-else
              v-bk-tooltips.right="{ theme: 'light', content: log.process_id }"
              v-dashed="9"
              style="cursor: pointer;"
            >{{ processIdSlice(log.process_id) }}</span>
          </div>
          <template v-if="streamLogFilters.length">
            <span
              class="pod-name"
              style="cursor: default;"
            >{{ log.podShortName }}</span>
          </template>
          <template v-else>
            <div
              class="pod-name"
              @click="handleAddStreamLogFilters(log)"
            >
              <span
                v-bk-tooltips.right="{ theme: 'light', content: $t('点击后，仅展示该实例日志') }"
                v-dashed="9"
              >{{ log.podShortName }}</span>
            </div>
          </template>
          <pre
            class="message"
            v-html="log.message || '--'"
          />
        </li>
      </ul>
      <div
        v-else
        class="ps-no-result"
      >
        <div
          v-if="!isStreamLogListLoading"
          class="text"
        >
          <!-- 异常处理 -->
          <empty-dark
            :keyword="emptyDarkConf.keyword"
            :abnormal="emptyDarkConf.isAbnormal"
            :empty-title="emptyDarkConf.title"
            @clear-filter="clearFilter"
            @reacquire="getStreamLogList"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script>import moment from 'moment';
import xss from 'xss';
import appBaseMixin from '@/mixins/app-base-mixin';
import logFilter from './comps/log-filter.vue';
import { formatDate } from '@/common/tools';

const xssOptions = {
  whiteList: {
    'bk-highlight-mark': [],
  },
};
const logXss = new xss.FilterXSS(xssOptions);
const initEndDate = moment().format('YYYY-MM-DD HH:mm:ss');
const initStartDate = moment().subtract(1, 'hours')
  .format('YYYY-MM-DD HH:mm:ss');

export default {
  components: {
    logFilter,
  },
  mixins: [appBaseMixin],
  data() {
    return {
      tabActive: 'standartLog',
      filterKeyword: '',
      contentHeight: 400,
      renderIndex: 0,
      renderFilter: 0,
      routeChangeIndex: 0,
      isLoading: true,
      tableMaxWidth: 700,
      isStreamLogListLoading: true,
      isScrollLoading: false,
      hasNextStreamLog: true,
      isShowDate: true,
      lastScrollId: '',
      initDateTimeRange: [initStartDate, initEndDate],
      lastTopStreamLog: null,
      autoTimer: 0,
      streamLogCount: 0,
      streamLogList: [],
      searchFilterKey: [],
      streamLogFilters: [],
      envList: [],
      processList: [],
      streamList: [],
      logParams: {
        start_time: initStartDate,
        end_time: initEndDate,
        environment: '',
        process_id: '',
        stream: '',
        keyword: '',
        levelname: '',
        time_range: '1h',
      },
      isFilter: false,
      emptyDarkConf: {
        keyword: '',
        isAbnormal: false,
        title: this.$t('暂无数据'),
      },
    };
  },
  watch: {
    'logParams.keyword'(newVal, oldVal) {
      if (newVal === '' && oldVal !== '') {
        if (this.isFilter) {
          this.loadData(false);
          this.isFilter = false;
        }
      }
    },
    '$route.params'(newVal, oldVal) {
      if (newVal.id !== oldVal.id || newVal.moduleId !== oldVal.moduleId) {
        this.isLoading = true;
        // eslint-disable-next-line no-plusplus
        this.renderIndex++;
        // eslint-disable-next-line no-plusplus
        this.routeChangeIndex++;
        this.resetParams();
        this.loadData();
      }
    },
  },
  beforeRouteLeave(to, from, next) {
    clearInterval(this.autoTimer);
    this.resetParams();
    next(true);
  },
  created() {
    const query = this.$route.query || {};
    this.logParams = {
      start_time: query.start_time || initStartDate,
      end_time: query.end_time || initEndDate,
      environment: query.environment || '',
      process_id: query.process_id || '',
      stream: query.stream || '',
      keyword: query.keyword || '',
      levelname: query.levelname || '',
      time_range: query.time_range || '1h',
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    /**
             * 初始化入口
             */
    init() {
      this.isLoading = true;
      this.loadData();

      const winHeight = window.innerHeight;
      const height = winHeight - 400;
      if (height > 400) {
        this.contentHeight = height;
      }
    },

    handleTabChange(name) {
      this.resetParams();
      this.loadData();
    },

    bindScrollLoading() {
      this.$refs.logContainer.addEventListener('scroll', this.scrollLoadStreamLog);
    },

    unbindScrollLoading() {
      this.$refs.logContainer.removeEventListener('scroll', this.scrollLoadStreamLog);
    },

    scrollLoadStreamLog() {
      const { logContainer } = this.$refs;
      const { scrollTop } = logContainer;

      // 滚动到底部
      if (scrollTop === 0) {
        if (this.isScrollLoading) {
          return false;
        }
        this.isScrollLoading = true;

        this.loadData();
      }
    },

    /**
             * 选择自定义时间，并确定
             */
    handlePickSuccess(params) {
      this.logParams = params;
      this.resetStreamLog();
      this.loadData();
    },

    /**
             * 清空查询参数
             */
    removeFilterParams() {
      if (this.$refs.bkSearcher && this.$refs.bkSearcher.removeAllParams) {
        this.$refs.bkSearcher.removeAllParams();
      }
    },

    getParams() {
      return {
        start_time: this.logParams.start_time,
        end_time: this.logParams.end_time,
        time_range: this.logParams.time_range,
        log_type: 'STANDARD_OUTPUT',
      };
    },

    /**
             * 构建过滤参数
             */
    getFilterParams() {
      const params = {
        query: {
          query_string: this.logParams.keyword,
        },
      };

      const filters = this.streamLogFilters;
      filters.forEach((filter) => {
        if (!params.query.terms) {
          params.query.terms = {};
        }
        params.query.terms[filter.key] = [filter.value];
      });

      if (this.logParams.process_id) {
        if (!params.query.terms) {
          params.query.terms = {};
        }
        params.query.terms.process_id = [this.logParams.process_id];
      }

      if (this.logParams.environment) {
        if (!params.query.terms) {
          params.query.terms = {};
        }
        params.query.terms.environment = [this.logParams.environment];
      }

      if (this.logParams.stream) {
        if (!params.query.terms) {
          params.query.terms = {};
        }
        params.query.terms.stream = [this.logParams.stream];
      }
      return params;
    },

    /**
             * 加载所有数据
             */
    loadData(isLoadFilter = true, isMaskLayer) {
      // 限制在一天内
      const startDay = moment(this.logParams.start_time).add(1, 'day');
      const endDay = moment(this.logParams.end_time);
      if (startDay.valueOf() < endDay.valueOf()) {
        this.$bkMessage({
          theme: 'error',
          message: this.$t('请重新选择时间范围，最长不超过一天'),
        });
        return false;
      }
      this.$refs.standartLogFilter.setAutoLoad();
      // 标准化输出
      this.getStreamLogList(isMaskLayer);
      isLoadFilter && this.getFilterData();
    },

    /**
             * 清空查询条件
             */
    clearConditionParams() {
      this.logParams.environment = '';
      this.logParams.process_id = '';
      this.logParams.stream = '';
      this.logParams.levelname = '';
    },

    /**
             * 重围搜索参数
             */
    resetParams() {
      this.initDateTimeRange = [initStartDate, initEndDate];
      this.lastScrollId = '';
      this.streamLogList = [];
      this.streamLogFilters = [];
      this.envList = [];
      this.streamList = [];
      this.processList = [];
      this.streamLogCount = 0;
      this.hasNextStreamLog = true;
      this.lastTopStreamLog = null;
      this.isScrollLoading = false;
      this.logParams = {
        start_time: initStartDate,
        end_time: initEndDate,
        environment: '',
        process_id: '',
        stream: '',
        keyword: '',
        time_range: '1h',
        levelname: '',
      };
    },

    /**
             * 关键字高亮
             * @param {String} text 匹配字符串
             */
    setKeywordHight(text) {
      const keywords = this.logParams.keyword.split(';');
      if (keywords.length) {
        keywords.forEach((keyword) => {
          keyword = keyword.trim();
          if (keyword) {
            const tpl = `<span class="ps-keyword-hightlight">${keyword}</span>`;
            const strReg = new RegExp(keyword, 'ig');
            text = text.replace(strReg, tpl);
          }
        });
        return text;
      }
      return text;
    },

    highlight(message) {
      return message.replace(/\[bk-mark\]/g, '<bk-highlight-mark>').replace(/\[\/bk-mark\]/g, '</bk-highlight-mark>');
    },

    /**
             * 获取标准输出日志数据
             * @param  {Number} page 第几页数据
             */
    async getStreamLogList(isMaskLayer) {
      const { appCode } = this;
      const moduleId = this.curModuleId;
      const params = this.getParams();
      const filter = this.getFilterParams();

      if (this.lastScrollId && this.isScrollLoading) {
        params.scroll_id = this.lastScrollId;
      }

      this.lastScrollId = '';
      if (isMaskLayer) {
        this.isStreamLogListLoading = true;
      }
      this.unbindScrollLoading();
      this.lastTopStreamLog = document.querySelector('.stream-log');
      try {
        const res = await this.$store.dispatch('log/getStreamLogList', {
          appCode,
          moduleId,
          params,
          filter,
        });
        this.lastScrollId = res.scroll_id;
        const data = res.logs.reverse();
        data.forEach((item) => {
          item.message = this.highlight(logXss.process(item.message));
          item.podShortName = item.pod_name.split('-').reverse()[0];
        });

        if (!data.length) {
          this.hasNextStreamLog = false;
        } else {
          this.bindScrollLoading();
        }
        this.streamLogCount = res.total;

        if (this.isScrollLoading) {
          this.streamLogList = [...data, ...this.streamLogList];
          setTimeout(() => {
            this.$refs.logContainer.scrollTop = data.logs * 35;
          }, 0);
        } else {
          this.streamLogList.splice(0, this.streamLogList.length, ...data);
          setTimeout(() => {
            // 滚动到底部
            this.$refs.logContainer.scrollTop = this.$refs.logContainer.scrollHeight;
          }, 0);
        }
        this.updateEmptyDarkConfig();
        this.emptyDarkConf.isAbnormal = false;
        this.emptyDarkConf.title = this.$t('暂无数据');
      } catch (res) {
        this.streamLogList = [];
        // 日志数据 - 普通应用（去除弹窗，将空状态转为异常）
        this.emptyDarkConf.isAbnormal = true;
        this.emptyDarkConf.title = this.$t('数据获取异常');
      } finally {
        setTimeout(() => {
          this.isStreamLogListLoading = false;
          this.isScrollLoading = false;
          this.isLoading = false;
        }, 500);
      }
    },

    async getFilterData() {
      const { appCode } = this;
      const moduleId = this.curModuleId;
      const params = this.getParams();

      try {
        const res = await this.$store.dispatch('log/getFilterData', { appCode, moduleId, params });
        const data = res;
        data.forEach((item) => {
          const condition = {
            id: item.key,
            name: item.name,
            text: item.chinese_name || item.name,
            list: [],
          };
          item.options.forEach((option) => {
            condition.list.push({
              id: option[0],
              text: option[0],
            });
          });
          if (condition.name === 'environment') {
            this.envList = condition.list;
          } else if (condition.name === 'process_id') {
            this.processList = condition.list;
          } else if (condition.name === 'stream') {
            this.streamList = condition.list;
          }
        });
        this.$refs.customLogFilter && this.$refs.customLogFilter.handleSetParams();
      } catch (res) {
        this.envList = [];
        this.processList = [];
        this.streamList = [];
      }
    },

    handleLogSearch(params) {
      this.logParams = params;
      this.resetStreamLog();
      this.loadData(params.isDateChange);

      const query = Object.assign({}, this.$route.query, params);
      this.$router.push({
        name: 'appLog',
        params: this.$route.params,
        query,
      });
    },

    handleLogReload(params) {
      this.resetStreamLog();
      this.loadData(false, params);
    },

    resetStreamLog() {
      const { logContainer } = this.$refs;
      this.unbindScrollLoading();
      logContainer.scrollTop = 0;
      this.lastScrollId = '';
      this.lastTopStreamLog = null;
      this.isScrollLoading = false;
      this.hasNextStreamLog = true;
    },

    searchLog(params) {
      this.logParams.environment = '';
      this.logParams.process_id = '';
      this.logParams.stream = '';
      this.logParams.levelname = '';

      params.forEach((item) => {
        const type = item.id;
        const selectItem = item.value;
        this.logParams[type] = selectItem.id;
      });
      this.loadData(false);
    },

    handleAddStreamLogFilters(log) {
      this.streamLogFilters.push({
        key: 'pod_name',
        value: log.pod_name,
        text: log.podShortName,
      });
      this.resetStreamLog();
      this.loadData(false);
    },

    handleClearStreamLogFilters() {
      this.streamLogFilters = [];
      this.renderIndex++;
      this.loadData(false);
    },

    processIdSlice(str) {
      return `${str.slice(0, 4)}.`;
    },

    clearFilter() {
      this.$refs.standartLogFilter && this.$refs.standartLogFilter.clearKeyword();
      this.isStreamLogListLoading = true;
    },

    updateEmptyDarkConfig() {
      this.emptyDarkConf.keyword = this.logParams.keyword;
    },

    formatTime(time) {
      return time ? formatDate(time * 1000) : '--';
    },
  },
};
</script>

<style lang="scss" scoped>
    @import '~@/assets/css/mixins/ellipsis.scss';

    .result {
        position: relative;
    }

    .log-filter-box input[type="checkbox"] {
        appearance: none;
    }

    .logSelectPanel {
        display: inline-block;
        width: 112px;
    }

    .log-filter-box {
        padding: 15px 0 20px 0;

        .flex{
            display: flex;
        }

        .form-item {
            display: flex;
            .bk-label {
                line-height: 32px;
            }
            .bk-form-content {
                flex: 1;
                position: relative;
            }
        }

        /deep/ .bk-date-picker.long {
            width: 100%;
        }
    }

    .query-text {
        padding: 0 30px 0 10px;
        width: 304px;
        height: 32px;
        line-height: 32px;
        border-radius: 2px;
        border: solid 1px #c4c6cc;
        font-size: 12px;
        float: left;
        color: #666;
    }

    .query-date {
        position: relative;
        width: 269px;
        height: 36px;
        float: left;
    }

    .query-date .query-text {
        width: 227px;
        padding: 0 30px 0 10px;
        cursor: pointer;
        background: url(/static/images/query-date-icon.png) 270px center no-repeat;
        font-size: 13px;
    }

    .query-date:after {
        content: "";
        position: absolute;
        width: 1px;
        height: 34px;
        background: #ccc;
        top: 1px;
        left: 407px;
    }

    .chart-box {
        min-height: 150px;
        background: #FAFBFD;
    }

    .result {
        margin: 20px 0;
    }

    .ps-log-table {
        &:before {
            display: none;
        }
        width: 100%;
        box-sizing: border-box;

        th {
            border-top: none;
            border-right: none;
        }
        td {
            font-size: 12px;
            color: #63656E;
        }
    }

    .ps-toggle-btn {
        margin-right: 4px;
        cursor: pointer;
        color: #C4C6CC;
        font-size: 12px;
    }

    .log-message {
        line-height: 20px;
        font-size: 13px;

        pre {
            position: relative;
            max-height: 40px;

            @include multiline-ellipsis;
        }
    }

    .detail-box {
        padding: 5px 0;
        background-color: #fafbfd;

        li {
            display: flex;
            padding: 0 10px;
            margin-top: 4px;
        }

        .key {
            display: block;
            min-width: 130px;
            line-height: 18px;
            text-align: right;
            padding-right: 10px;
            white-space: nowrap;
            margin-top: -3px;
        }

        .value {
            line-height: 18px;
            display: block;
            flex: 1;
            font-family: "Helvetica Neue", Helvetica, Tahoma, Arial, "Microsoft Yahei", "PingFang SC", STHeiTi, sans-serif;
        }
    }

    .ps-search-btn {
        margin: 13px 0 0 12px;
        width: 126px;
        line-height: 18px;
        vertical-align: middle;
    }

    .page-wrapper {
        margin-top: 15px;
        text-align: right;

        .bk-page {
            float: right;
        }
    }

    .clear-keyword-btn {
        position: absolute;
        right: 10px;
        top: 10px;
        cursor: pointer;
    }

    .default-time-text {
        position: absolute;
        left: 0;
        top: 0;
        margin: 1px;
        padding: 0 10px;
        width: 290px;
        height: 30px;
        background: #fff;
        z-index: 1;
        box-sizing: border-box;
        line-height: 30px;
        cursor: pointer;
        pointer-events: none;
        font-size: 12px;
    }

    .time-th {
        width: 200px;
    }

    .ps-checkbox-default {
        height: 16px;
        margin-top: 8px;
    }

    .log-main {
        display: flex;
        margin-top: 20px;

        .log-fields {
            width: 210px;
            min-width: 210px;
            background: #F5F6FA;
            border-radius: 2px;
            padding: 15px 10px 15px 20px;

            .title {
                font-size: 14px;
                color: #313238;
                margin-bottom: 18px;
            }

            .field-list {
                max-height: 1200px;
                overflow: auto;
                /deep/ .bk-form-checkbox {
                    display: block;
                    margin-bottom: 15px;

                    & + .bk-form-checkbox {
                        margin-left: 0;
                    }
                }
            }
        }

        .log-content {
            flex: 1;
            padding-left: 10px;
        }
    }
    .table-filters {
        margin: 10px 0 0 0;
        display: flex;
        align-items: center;

        .clear-filters-btn {
            cursor: pointer;
        }

        .filter-list {
            > li {
                background: #F0F1F5;
                border-radius: 2px;
                padding: 0 6px 0 6px;
                color: #63656E;
                font-size: 12px;
                display: inline-block;
                margin-right: 10px;

                .filter-value {
                    max-width: 300px;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    white-space: nowrap;
                    display: inline-block;
                    line-height: 26px;
                    vertical-align: middle;
                }

                .paasng-icon {
                    font-size: 20px;
                    vertical-align: middle;
                    cursor: pointer;
                }
            }
        }
    }

    .ps-log-header {
        background: #313238;
        border-bottom: 1px solid #000;
        padding: 10px 20px;
        margin-top: 20px;
        border-radius: 2px 2px 0 0;

        .text {
            margin-left: 5px;
            color: #fff;
            display: inline-block;
            vertical-align: middle;
            font-size: 12px;
        }
    }
    .ps-log-container {
        background: #313238;
        border-radius: 0 0 2px 2px;
        padding: 20px;
        color: #DCDEE5;
        font-size: 12px;
        line-height: 18px;
        overflow: auto;
        min-height: 300px;
        position: relative;
        margin-bottom: -40px;

        &::-webkit-scrollbar {
            width: 4px;
            background-color: lighten(transparent, 80%);
        }
        &::-webkit-scrollbar-thumb {
            height: 5px;
            border-radius: 2px;
            background-color: #63656e;
        }

        .ps-no-result {
            width: 80%;
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
        }

        .stream-log {
            display: flex;
            margin-bottom: 8px;
            font-family: Consolas, "source code pro", "Bitstream Vera Sans Mono", Consolas, Courier, monospace, "微软雅黑", "Arial";

            .pod-name {
                min-width: 95px;
                text-align: right;
                margin-right: 15px;
                color: #979BA5;
                cursor: pointer;

                &:hover {
                    color: #3a84ff;
                }
            }
            .message {
                flex: 1;
            }
        }
    }
    .scroll-loading {
        height: 40px;
        overflow: hidden;
        border-radius: 2px;
        margin-bottom: 10px;
    }
    .no-data {
        padding: 8px;
        text-align: center;
        background: #1d1e22;
        margin-bottom: 10px;
        border-radius: 2px;
    }
    .table-wrapper {
        width: auto;
    }
    .tooltip-icon {
        cursor: pointer;
        vertical-align: middle;
    }
    .not-found {
        padding: 25px 0 10px 0;
        color: #333;
        font-size: 12px;
    }
    .mouseStyle {
        cursor: default;
        display: inline-block;
        width: 46px;
    }
</style>
